<?php
namespace Pinenut;

/**
 * Контейнер зависимостей приложения
 *
 * Декларирует их для определённых классов
 * Внедряет статические зависимости при инициализации контейнера
 * Внедряет зависимости при создании объекта (или когда нужно)
 *
 * TODO: всё же придумать автоматическое ленивое внедрение при первом обращении
 * хм, а может внедрять вначале заглушку, которая, при обращении
 * будет уже внедрять саму зависимость вместо себя?
 *
 * @author Kosh
 *        
 */
class Di {
	
	protected $config;

	/**
	 * Конструктор
	 * Получаем конфу, внедряем статические зависимости
	 * 
	 * 
	 * @param \Pinenut\Config $configClass
	 */
	function __construct($configClass) {
		
		if (!class_exists($configClass)) return;
		
		$this->config = $configClass::get();
		//print_r($this->config); exit;
		// получаем конфу с прописаными зависимости и проинициированными объектами 
		// или функциями, возвращаеющими зависимости

		$classes = array_unique(array_merge(
				array_keys((array)@$this->config['staticDependencies']), 
				array_keys((array)@$this->config['objectDependencies'])
			));
		
		foreach ($classes as $class) {
			if (method_exists($class, 'setDiContainer')) {
				// устанавливаем контейнер
				$class::setDiContainer ( $this );
			}
		}
		
		foreach ( (array)@$this->config['staticDependencies'] as $class => $injections ) {
			if (method_exists($class, 'setDiContainer')) {
				
				// внедряем статические зависимости
				foreach ( $injections as $property => $dependencyName ) {
					$dependency = $this->getDependency($dependencyName);
					$class::injectStatic ( $property, $dependency );
				}
			}
		}
	}
	
	/**
	 * Внедряем зависимости для указанного объекта
	 * @param unknown $object
	 */
	function di($object) {
	
		// если объект не поддерживает инъекции - игнорируем его
		// TODO: надо что-нить сказать правильное и исключительное
		if (!method_exists($object, 'inject')) 
			return;
		
		$class = get_class ( $object );
		$parents = class_parents ( $object );
		$parents [$class] = $class;
		foreach ( $this->config['objectDependencies'] as $class => $injections ) {
			if (in_array ( $class, $parents )) {
				foreach ( $injections as $property => $dependencyName ) {
					$dependency = $this->getDependency($dependencyName);
					$object->inject ( $property, $dependency );
				}
			}
		}
	}
	
	/**
	 * Возвращаем зависимость
	 * если функция для получаения зависимости - исполняем её
	 * 
	 * @param string $name
	 * @return Ambiguous	 
	 */
	
	function getDependency($name) {
		$dependency = @$this->config['dependencies'][$name];
		if (is_callable($dependency)) {
			$dependency = $dependency();
		}
		return $dependency;
	}
}